package com.want.config;

import cn.dev33.satoken.jwt.StpLogicJwtForStateless;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.strategy.SaStrategy;
import com.want.api.user.UserApi;
import com.want.controller.TestController;
import com.want.model.dto.Result;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.Order;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.HandlerAdapter;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author WangZhiJian
 * @since 2021/12/20 0020
 */
@Slf4j
@Configuration
public class SaTokenConfigure {
    // Sa-Token 整合 jwt (Style模式)
    @Bean
    public StpLogic getStpLogicJwt() {
        return new StpLogicJwtForStateless();
    }
    /**
     * 注册 [Sa-Token全局过滤器]
     */
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        return new SaReactorFilter()
                // 指定 [拦截路由]
                .addInclude("/front/**","/test/**")
                // 指定 [放行路由]
                // 指定[认证函数]: 每次请求执行
                .setAuth(obj -> {
                    SaRouter.match("/test/test", () -> {
                        StpUtil.checkLogin();
                    });
                })
                // 指定[异常处理函数]：每次[认证函数]发生异常时执行此函数
                .setError(e -> {
                    return TestController.AjaxJson.getError(e.getMessage());
                })
                ;
    }

    @Bean
    @Order(0)
    public HandlerAdapter saTokenHandlerAdapter(RequestMappingHandlerAdapter requestMappingHandlerAdapter){
        return new HandlerAdapter() {
            @Override
            public boolean supports(Object handler) {
                return requestMappingHandlerAdapter.supports(handler);
            }

            @Override
            public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                Method method = handlerMethod.getMethod();
                log.info("method is {}",method);
                SaStrategy.me.checkMethodAnnotation.accept(method);
                return requestMappingHandlerAdapter.handle(exchange,handler);
            }
        };
    }
    /**
     * 重写 Sa-Token 框架内部算法策略
     */
    @Autowired
    public void rewriteSaStrategy() {
        // 重写Sa-Token的注解处理器，增加注解合并功能
        SaStrategy.me.getAnnotation = (element, annotationClass) -> {
            return AnnotatedElementUtils.getMergedAnnotation(element, annotationClass);
        };
    }
    @Bean
    public StpInterface stpInterface(UserApi userApi){
        return new StpInterface() {
            /**
             * 返回一个账号所拥有的权限码集合
             */
            @Override
            public List<String> getPermissionList(Object loginId, String loginType) {
                // 本list仅做模拟，实际项目中要根据具体业务逻辑来查询权限
                List<String> list = new ArrayList<String>();
                return list;
            }

            /**
             *
             * TODO 这里 需要记录
             * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
             */
            @SneakyThrows
            @Override
            public List<String> getRoleList(Object loginId, String loginType) {
                // 本list仅做模拟，实际项目中要根据具体业务逻辑来查询角色
                AtomicReference<List<String>> userRoleList = new AtomicReference<>();
                CountDownLatch countDownLatch = new CountDownLatch(1);
                userApi.userRole(Integer.valueOf(loginId.toString()))
                        .filter(Result::success)
                        .map(Result::getData)
                        .switchIfEmpty(Mono.defer(() -> Mono.just(new ArrayList<String>(0))))
                        .timeout(Duration.ofSeconds(3))
                        .subscribe(list -> {
                            userRoleList.set(list);
                            countDownLatch.countDown();
                        });
                countDownLatch.await();
                log.info("userRoleList is {}",userRoleList);
                return userRoleList.get();
            }
        };

    }
}

